package cn.bincker.test;

import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
import org.springframework.transaction.*;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

@Component
public class MixinTransactionManager implements PlatformTransactionManager {
    private final List<PlatformTransactionManager> transactionManagers;

    public MixinTransactionManager(PlatformTransactionManager firstTransactionManager, @Qualifier("secondDataSourceTransactionManager") PlatformTransactionManager secondTransactionManager) {
        this.transactionManagers = Arrays.asList(firstTransactionManager, secondTransactionManager);
    }

    @Override
    public TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException {
        return new MixinTransactionStatus(transactionManagers.stream().map(m->m.getTransaction(definition)).collect(Collectors.toList()));
    }

    @Override
    public void commit(TransactionStatus status) throws TransactionException {
        if(!status.getClass().equals(MixinTransactionStatus.class)) throw new TransactionSystemException("status class is not equals MixinTransactionStatus");
        MixinTransactionStatus mixinTransactionStatus = (MixinTransactionStatus) status;
        for (int i = 0; i < mixinTransactionStatus.transactionStatusList.size(); i++) {
            transactionManagers.get(i).commit(mixinTransactionStatus.transactionStatusList.get(i));
        }
    }

    @Override
    public void rollback(TransactionStatus status) throws TransactionException {
        if(!status.getClass().equals(MixinTransactionStatus.class)) throw new TransactionSystemException("status class is not equals MixinTransactionStatus");
        MixinTransactionStatus mixinTransactionStatus = (MixinTransactionStatus) status;
        for (int i = mixinTransactionStatus.transactionStatusList.size() - 1; i >= 0; i--) {//从顶部回滚会导致底部事务被提前关闭
            transactionManagers.get(i).rollback(mixinTransactionStatus.transactionStatusList.get(i));
        }
    }

    private static class MixinTransactionStatus implements TransactionStatus{
        private final List<TransactionStatus> transactionStatusList;

        public MixinTransactionStatus(List<TransactionStatus> transactionStatusList) {
            this.transactionStatusList = transactionStatusList;
        }

        @Override
        public boolean hasSavepoint() {
            return transactionStatusList.stream().anyMatch(TransactionStatus::hasSavepoint);
        }

        @Override
        public void flush() {
            transactionStatusList.forEach(TransactionStatus::flush);
        }

        @Override
        public Object createSavepoint() throws TransactionException {
            return transactionStatusList.stream().map(TransactionStatus::createSavepoint).toArray();
        }

        @Override
        public void rollbackToSavepoint(Object savepoint) throws TransactionException {
            if(!savepoint.getClass().isArray()) throw new TransactionSystemException("savepoint error: " + savepoint);
            Object[] savePoints = (Object[]) savepoint;
            for (int i = 0; i < savePoints.length; i++) {
                transactionStatusList.get(i).rollbackToSavepoint(savePoints[i]);
            }
        }

        @Override
        public void releaseSavepoint(Object savepoint) throws TransactionException {
            if(!savepoint.getClass().isArray()) throw new TransactionSystemException("savepoint error: " + savepoint);
            Object[] savePoints = (Object[]) savepoint;
            for (int i = 0; i < savePoints.length; i++) {
                transactionStatusList.get(i).releaseSavepoint(savePoints[i]);
            }
        }

        @Override
        public boolean isNewTransaction() {
            return transactionStatusList.stream().anyMatch(TransactionStatus::isNewTransaction);
        }

        @Override
        public void setRollbackOnly() {
            transactionStatusList.forEach(TransactionStatus::setRollbackOnly);
        }

        @Override
        public boolean isRollbackOnly() {
            return transactionStatusList.stream().allMatch(TransactionStatus::isRollbackOnly);
        }

        @Override
        public boolean isCompleted() {
            return transactionStatusList.stream().allMatch(TransactionStatus::isCompleted);
        }
    }
}
